﻿using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTKBasics.AssimpNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OpenTKBasics
{
    // So you've drawn the first triangle. But what about drawing multiple?
    // You may consider just adding more vertices to the array, and that would technically work, but say you're drawing a rectangle.
    // It only needs four vertices, but since OpenGL works in triangles, you'd need to define 6.
    // Not a huge deal, but it quickly adds up when you get to more complex models. For example, a cube only needs 8 vertices, but
    // doing it that way would need 36 vertices!

    // OpenGL provides a way to reuse vertices, which can heavily reduce memory usage on complex objects.
    // This is called an Element Buffer Object. This tutorial will be all about how to set one up.
    public class Game6 : GameWindow
    {
        // We modify the vertex array to include four vertices for our rectangle.
        private readonly float[] _vertices =
        {
             0.5f,  0.5f, 0.0f, 1.0f,0.0f,0.0f,  1.0f, 1.0f,  // top right
             0.5f, -0.5f, 0.0f, 0.0f,1.0f,0.0f,  1.0f, 0.0f,  // bottom right
            -0.5f, -0.5f, 0.0f, 0.0f,0.0f,1.0f,  0.0f, 0.0f,  // bottom left
            -0.5f,  0.5f, 0.0f, 0.0f,0.0f,1.0f,  0.0f, 1.0f,  // top left
        };

        // Then, we create a new array: indices.
        // This array controls how the EBO will use those vertices to create triangles
        private readonly uint[] _indices =
        {
            // Note that indices start at 0!
            0, 1, 3, // The first triangle will be the bottom-right half of the triangle
            1, 2, 3  // Then the second will be the top-right half of the triangle
        };

        private int _vertexBufferObject;
        private int _vertexArrayObject;
        // Add a handle for the EBO
        private int _elementBufferObject;

        private Shader _shader;
        private Camera _camera;
        private Texture2 _texture;
        private Mesh cube;

        float aspect = 2.0f;
        double timeValue = 0.0d;
        Matrix4 transform;


        public Game6(int width = 1920, int height = 1080, string title = "Game6")
            : base(
                  GameWindowSettings.Default,
                  new NativeWindowSettings()
                  {
                      Size = new Vector2i(width, height),
                      Title = title,
                      // This is needed to run on macos
                      Flags = ContextFlags.ForwardCompatible,
                      WindowBorder = WindowBorder.Fixed,
                      StartFocused = true,
                      API = ContextAPI.OpenGL,
                      Profile = ContextProfile.Core,
                      APIVersion = new Version(3, 3),

                      //StartVisible = false,  //请勿开启 危险
                  }
                  )
        {
            this.CenterWindow();
        }

        protected override void OnLoad()
        {
            base.OnLoad();

            _shader = new Shader("shader/shader.vert", "shader/shader.frag");
            _shader.Use();

            GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);

            //_texture = new Texture2(0);
            //_texture = _texture.LoadFromFile("texture/wood.jpg");
            //_texture.Use(TextureUnit.Texture0);

            cube = new Mesh(_shader, _vertices, _indices);

            //相机生成
            _camera = new Camera(3.0f * Vector3.UnitZ, aspect);

        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            GL.Clear(ClearBufferMask.ColorBufferBit);

            //_texture.Use(TextureUnit.Texture0);
            //_shader.Use();
            // Because ElementArrayObject is a property of the currently bound VAO,
            // the buffer you will find in the ElementArrayBuffer will change with the currently bound VAO.
            //GL.BindVertexArray(_vertexArrayObject);

            // Then replace your call to DrawTriangles with one to DrawElements
            // Arguments:
            //   Primitive type to draw. Triangles in this case.
            //   How many indices should be drawn. Six in this case.
            //   Data type of the indices. The indices are an unsigned int, so we want that here too.
            //   Offset in the EBO. Set this to 0 because we want to draw the whole thing.
            //GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);

            cube.Draw();

            SwapBuffers();
        }

        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);

            {
                //对shader传值 导致颜色变化
                timeValue += 0.02d;
                float greenValue = (float)Math.Sin(timeValue) / (2.0f + 0.5f);
                int vertexColorLocation = GL.GetUniformLocation(this._shader.GetHandle(), "uColor");
                GL.Uniform4(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

                //定义模型变换矩阵
                Matrix4 scale = Matrix4.CreateScale(0.5f, 0.5f, 0.5f);
                Matrix4 rotation = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(100.0f));
                Matrix4 trans = Matrix4.CreateTranslation(-0.5f, 0.2f, 0.0f);
                transform = trans * rotation * scale;

                //矩阵整合进入shader 对shader传矩阵
                float rotValue = (float)Math.Sin(timeValue) * 80.0f;
                Matrix4 model = Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(rotValue));
                model *= transform;
                Matrix4 view = _camera.GetViewMatrix();
                Matrix4 projection = _camera.GetProjectionMatrix();
                this._shader.SetMatrix4("model", model);
                this._shader.SetMatrix4("view", view);
                this._shader.SetMatrix4("projection", projection);
                //Console.WriteLine("UPDATE");
            }

            //var input = KeyboardState;

            //if (input.IsKeyDown(Keys.Escape))
            //{
            //    Close();
            //}

            //input
            {
                // Check to see if the window is focused
                if (!IsFocused) { return; }
                KeyboardState input = KeyboardState;
                if (input.IsKeyDown(Keys.Escape)) { Close(); }
                _camera.KeyControllor(input, e);

                MouseState mouse = MouseState;
                _camera.MouseMoveControllor(mouse);
            }

        }

        protected override void OnResize(ResizeEventArgs e)
        {
            base.OnResize(e);
            aspect = e.Width / (float)e.Height;
            GL.Viewport(0, 0, e.Width, e.Height);
        }

        protected override void OnUnload()
        {
            base.OnUnload();

            GL.BindVertexArray(0);
            GL.DeleteVertexArray(this._vertexArrayObject);

            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.DeleteBuffer(this._vertexBufferObject);

            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
            GL.DeleteBuffer(this._elementBufferObject);

            _shader.Dispose();
        }

        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            base.OnMouseWheel(e);
            _camera.Fov -= e.OffsetY;
        }

         
        #region selection method
        //==================================================================================================
        //private void OnMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        //{
        //    if (mouseButtonEventArgs.Button != MouseButton.Left)
        //        return;

        //    MouseState mouse =  MouseState;
        //    PickObjectOnScreen(mouse.X, mouse.Y,Size.X,Size.Y);
        //}

        ///// <summary>
        ///// 这是从相机和屏幕中计算光线的方法。它需要鼠标坐标，然后将转换返回给世界空间。
        ///// </summary>
        ///// <param name="mouseX"></param>
        ///// <param name="mouseY"></param>
        ///// <param name="Width"></param>
        ///// <param name="Height"></param>
        //private void PickObjectOnScreen(float mouseX, float mouseY,int Width,int Height)
        //{
        //    // heavily influenced by: http://antongerdelan.net/opengl/raycasting.html
        //    // viewport coordinate system
        //    // normalized device coordinates
        //    var x = (2f * mouseX) / Width - 1f;
        //    var y = 1f - (2f * mouseY) / Height;
        //    var z = 1f;
        //    var rayNormalizedDeviceCoordinates = new Vector3(x, y, z);

        //    // 4D homogeneous clip coordinates
        //    var rayClip = new Vector4(rayNormalizedDeviceCoordinates.X, rayNormalizedDeviceCoordinates.Y, -1f, 1f);

        //    // 4D eye (camera) coordinates
        //    var _projectionMatrix = _camera.GetProjectionMatrix(); ;
        //    var rayEye = _projectionMatrix.Inverted() * rayClip;
        //    rayEye = new Vector4(rayEye.X, rayEye.Y, -1f, 0f);

        //    // 4D world coordinates
        //    var _lookAtMatrix = _camera.GetViewMatrix(); ;
        //    //var rayWorldCoordinates = (_camera.LookAtMatrix.Inverted() * rayEye).Xyz;
        //    var rayWorldCoordinates = (_lookAtMatrix.Inverted() * rayEye).Xyz;
        //    rayWorldCoordinates.Normalize();
        //    FindClosestAsteroidHitByRay(rayWorldCoordinates);
        //}

        ///// <summary>
        ///// 从以下内容调用上述方法，迭代当前在游戏中活动的所有对象，然后尝试找到最接近的对象（如果有许多）到光线的原点。
        ///// </summary>
        ///// <param name="rayWorldCoordinates"></param>
        //private void FindClosestAsteroidHitByRay(Vector3 rayWorldCoordinates)
        //{
        //    AGameObject bestCandidate = null;
        //    double? bestDistance = null;
        //    foreach (var gameObject in _gameObjects)
        //    {
        //        if (!(gameObject is SelectableSphere) && !(gameObject is Asteroid))
        //            continue;
        //        var candidateDistance = gameObject.IntersectsRay(rayWorldCoordinates, _camera.Position);
        //        if (!candidateDistance.HasValue)
        //            continue;
        //        if (!bestDistance.HasValue)
        //        {
        //            bestDistance = candidateDistance;
        //            bestCandidate = gameObject;
        //            continue;
        //        }
        //        if (candidateDistance < bestDistance)
        //        {
        //            bestDistance = candidateDistance;
        //            bestCandidate = gameObject;
        //        }
        //    }
        //    if (bestCandidate != null)
        //    {
        //        switch (bestCandidate)
        //        {
        //            case Asteroid asteroid:
        //                _clicks += asteroid.Score;
        //                break;
        //            case SelectableSphere sphere:
        //                sphere.ToggleModel();
        //                break;
        //        }
        //    }
        //}

        ///// <summary>
        ///// 我们需要添加一个检查游戏对象是否已被光线命中的方法。
        ///// 将其添加到AgameObject类中，以便可以挑选所有游戏对象。
        ///// 基本上我们首先检查光线的起源是否位于我们正在检查的球体内。
        ///// 之后，我们检查光线是否指向物体，
        ///// 最后我们执行毕达哥拉斯方法来确定光线是否在我们正在寻找的球体的半径范围内
        ///// </summary>
        ///// <param name="rayDirection"></param>
        ///// <param name="rayOrigin"></param>
        ///// <returns></returns>
        //public double? IntersectsRay(Vector3 rayDirection, Vector3 rayOrigin)
        //{
        //    var radius = _scale.X;
        //    var difference = Position.Xyz - rayDirection;
        //    var differenceLengthSquared = difference.LengthSquared;
        //    var sphereRadiusSquared = radius * radius;
        //    if (differenceLengthSquared < sphereRadiusSquared)
        //    {
        //        return 0d;
        //    }
        //    var distanceAlongRay = Vector3.Dot(rayDirection, difference);
        //    if (distanceAlongRay < 0)
        //    {
        //        return null;
        //    }
        //    var dist = sphereRadiusSquared + distanceAlongRay * distanceAlongRay - differenceLengthSquared;
        //    var result = (dist < 0) ? null : distanceAlongRay - (double?)Math.Sqrt(dist);
        //    return result;
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        //public class SelectableSphere : AGameObject
        //{
        //    private ARenderable _original;
        //    private ARenderable _secondaryModel;
        //    public SelectableSphere(ARenderable model, ARenderable secondaryModel, Vector4 position, Vector4 direction, Vector4 rotation)
        //        : base(model, position, direction, rotation, 0)
        //    {
        //        _original = model;
        //        _secondaryModel = secondaryModel;
        //    }


        //    public override void Update(double time, double delta)
        //    {
        //        _rotation.Y = (float)((time + GameObjectNumber) * 0.5);
        //        var d = new Vector4(_rotation.X, _rotation.Y, 0, 0);
        //        d.Normalize();
        //        _direction = d;
        //        base.Update(time, delta);
        //    }

        //    public void ToggleModel()
        //    {
        //        if (_model == _original)
        //            _model = _secondaryModel;
        //        else
        //            _model = _original;
        //    }
        //}

        ///*
        // var maxX = 2.5f;
        //var maxY = 1.5f;
        //for (float x = -maxX; x < maxX; x += 0.5f)
        //{
        //    for (float y = -maxY; y < maxY; y += 0.5f)
        //    {
        //        _gameObjects.Add(_gameObjectFactory.CreateSelectableSphere(new Vector4(x, y, -5f, 0)));
        //    }
        //}
        //for (float x = -maxX; x < maxX; x += 0.65f)
        //{
        //    for (float y = -maxY; y < maxY; y += 0.65f)
        //    {
        //        _gameObjects.Add(_gameObjectFactory.CreateSelectableSphereSecondary(new Vector4(x, y, -6f, 0)));
        //    }
        //}
        // */

        //==================================================================================================
        #endregion
    }
}
